package com.redxiii.safesync.container;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redxiii.safesync.Archive;
import com.redxiii.safesync.CryptoTool;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class RemoteContainer extends LocalContainer {

	private static final Logger logger = LoggerFactory.getLogger(RemoteContainer.class);
	
	private ContainerDetail detail;
	private File containerFile;
	private XStream stream;
	
	@Override
	public void init(File rootFolder, CryptoTool cryptoTool) {
		
		verifyContainer(rootFolder, cryptoTool);
		
		stream = new XStream(new DomDriver());
		
		try {
			containerFile = new File(rootFolder,cryptoTool.getPasswordHash() + ".lck");
			if (containerFile.exists()) {
				ByteArrayOutputStream outStream = new ByteArrayOutputStream();
				cryptoTool.decrypt(new FileInputStream(containerFile), outStream);
				String xml = new String(outStream.toByteArray());
				detail = (ContainerDetail) stream.fromXML(xml);
			} else {
				detail = new ContainerDetail();
			}
		} catch (FileNotFoundException e) {
			throw new IllegalStateException("Already locked",e);
		} catch (IOException e) {
			throw new IllegalStateException("Invalid password",e);
		}
		
		super.init(rootFolder, cryptoTool);
		
		updateContainerDetail();
	}
	
	public static void verifyContainer(File rootFolder, CryptoTool cryptoTool) {
		try {
			
			String existingFilename = null;
			for (File f : rootFolder.listFiles()) {
				if (f.getName().endsWith(".lck") && f.isFile()) {
					existingFilename = f.getName();
					break;
				}
			}
			
			String fileName = cryptoTool.getPasswordHash() + ".lck";
			
			if (existingFilename != null && !existingFilename.equals(fileName)) {
				throw new IllegalStateException("Invalid password");
			}
		} catch (Exception e) {
			throw new IllegalStateException("Invalid password");
		}
	}
		
	public void updateContainerDetail() {
		try {
			detail.setSyncDate(System.currentTimeMillis());
			String xml = stream.toXML(detail);
			InputStream inStream = new ByteArrayInputStream(xml.getBytes());
			OutputStream outStream = new FileOutputStream(containerFile);
			cryptoTool.encrypt(inStream, outStream);
			outStream.flush();
			outStream.close();
		} catch (Exception e) {
			throw new IllegalStateException("Already locked",e);
		}
	}
	
	@Override
	protected void addArchiveFile(File file) {
		
		if (containerFile.equals(file))
			return;
		
		Archive archive = new Archive(file, rootFolder);
		if (detail.getArchivesNames().contains(archive.getAbsoluteName()) ) {
			
			long size = detail.getModifiedSize(archive.getAbsoluteName());
			long date = detail.getModifiedDate(archive.getAbsoluteName());
			
			archive.setSize(size);
			archive.setModifiedDate(date);
			
			archives.put(archive.getAbsoluteName(), archive);
		} else {
			logger.warn("File ignored: {}", file.getName());
		}
	}
	
	@Override
	public boolean update(Archive archive) {
		
		if (!archive.getFile().canRead()) {
			logger.warn("Could not read file {}", archive.getAbsoluteName());
			return false;
		}
		
		try {
			File newFile = new File(rootFolder.getAbsolutePath(),archive.getAbsoluteName());
			
			if (newFile.exists())
				newFile.delete();
			
			File fPath = new File(newFile.getParent());
			if (!fPath.exists())
				fPath.mkdirs();
			
			newFile.createNewFile();
			
			FileOutputStream outputStream = new FileOutputStream(newFile);
			FileInputStream inputStream = new FileInputStream(archive.getFile());
			
			cryptoTool.encrypt(inputStream, outputStream);
			
			Archive safeArchive = new Archive(newFile, rootFolder);
			archives.put(archive.getAbsoluteName(), safeArchive);
			detail.update(archive);
			
			return true;
		} catch (FileNotFoundException e) {
			logger.error("Error updating file {}",archive.getName(),e);
		} catch (IOException e) {
			logger.error("Error updating file {}",archive.getName(),e);
		}
		return false;
	}
	
	@Override
	public boolean remove(Archive archive) {
		detail.remove(archive);
		return super.remove(archive);
	}
	
	public Long getSyncDate() {
		if (detail.getSyncDate() == null)
			return 0L;
		return detail.getSyncDate();
	}
}
